package com.org.oracle.service.impl;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.google.common.base.Preconditions;
import com.org.oracle.constant.StudyPointsListConstants;
import com.org.oracle.enums.ResponseCodeEnum;
import com.org.oracle.exception.CustomException;
import com.org.oracle.mongodb.dto.PointsListDTO;
import com.org.oracle.mongodb.entity.StudyPointsList;
import com.org.oracle.mongodb.entity.User;
import com.org.oracle.service.StudyPointsListService;
import com.org.oracle.sub.entity.UserPoints;
import com.org.oracle.sub.vo.UserPointsVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.List;
import java.util.Objects;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.project;

/**
 * @Author: heiye
 * @CreateTime: 2024/08/29 下午2:34
 * @Description: 学习积分榜ServiceImpl
 * @Version: 1.0
 */
@Service
@Slf4j
public class StudyPointsListServiceImpl implements StudyPointsListService {
    @Resource
    private MongoTemplate mongoTemplate;

    @Resource
    private TransactionTemplate transactionTemplate;

    /**
     * 用户加入知识圈后加入学习积分榜
     *
     * @param uid
     */
    @Override
    public void pointsInit(String uid, String circleId) {
        // 校验参数
        Preconditions.checkArgument(StringUtils.isNotBlank(uid) && StringUtils.isNotBlank(circleId), ResponseCodeEnum.PARAM_REQUIRE);

        // 查找用户数据
        User user = mongoTemplate.findOne(Query.query(Criteria.where("uid").is(uid)), User.class);

        // 用户没有授权地址信息
        Preconditions.checkArgument(StringUtils.isNotBlank(user.getAddress()), ResponseCodeEnum.USER_ADDRESS_NOT_AUTHORIZATION);

        // 创建学习积分榜内用户数据
        UserPoints userPoints = UserPoints.builder()
                .uid(uid)
                .weekPoints(0)
                .monthPoints(0)
                .sumPoints(0)
                .address(user.getAddress())
                .build();

        // 构建查询条件
        Query query = new Query(Criteria.where("circle_id").is(circleId));

        // 构建添加
        Update update = new Update();
        update.addToSet("list", userPoints);

        mongoTemplate.updateFirst(query, update, StudyPointsList.class);
    }

    /**
     * 用户完成任务后更新榜单
     *
     * @param uid
     * @param circleId
     * @param points
     */
    @Override
    public void pointsUpdate(String uid, String circleId, Integer points) {
        // 校验参数
        Preconditions.checkArgument(StringUtils.isNotBlank(uid) && StringUtils.isNotBlank(circleId) && Objects.nonNull(points), ResponseCodeEnum.PARAM_REQUIRE);

        // 获取该用户积分数据
        UserPoints userPoints = getUserPointsOne(uid, circleId);

        // 更改里面积分数据
        userPoints.setWeekPoints(userPoints.getWeekPoints() + points);
        userPoints.setMonthPoints(userPoints.getMonthPoints() + points);
        userPoints.setSumPoints(userPoints.getSumPoints() + points);

        // 构建查询条件
        Query query = new Query(Criteria.where("circle_id").is(circleId));

        // 构建添加
        Update update = new Update();
        update.set("list.$[item]", userPoints);
        update.filterArray("item.uid", uid);

        mongoTemplate.updateFirst(query, update, StudyPointsList.class);
    }

    /**
     * 获取一位用户积分
     *
     * @param uid
     * @param circleId
     * @return
     */
    private UserPoints getUserPointsOne(String uid, String circleId) {
        // 构建聚合条件
        Aggregation aggregation = newAggregation(
                // 展开数组
                unwind("list"),
                // 构建查询条件
                match(Criteria.where("circle_id").is(circleId).and("list.uid").is(uid)),
                // 投影字段
                project("list").asArray("list"));

        // 得到数据并转化成StudyPointsList
        List<StudyPointsList> mappedResults = mongoTemplate.aggregate(aggregation, StudyPointsList.class, StudyPointsList.class).getMappedResults();

        // 解析数据
        // https://blog.csdn.net/xiao_hei_a/article/details/141759862?spm=1001.2014.3001.5502
        return mappedResults.get(0).getList().get(0);
    }

    /**
     * 查找榜单的积分排名
     *
     * @param dto
     * @return
     */
    @Override
    public List<UserPointsVO> findPointsList(PointsListDTO dto) {
        // 校验参数
        Preconditions.checkArgument(Objects.nonNull(dto), ResponseCodeEnum.PARAM_REQUIRE);
        Preconditions.checkArgument(StringUtils.isNotBlank(dto.getType()), ResponseCodeEnum.PARAM_REQUIRE);
        dto.checkParam();

        // 构建过滤条件
        Criteria criteria = Criteria.where("circle_id").is(dto.getCircleId());

        // 如果dto里地区参数不为空意味着查找的是地区榜单
        if (StringUtils.isNotEmpty(dto.getAddress())) {
            criteria.and("list.address").is(dto.getAddress());
        }

        // 提取dto里想要获取那个榜单数据字段
        // 获取出来数据格式：list.数组里不同积分数据字段
        // 非要现在获取字段，不到时在赋值积分字段？
        // 如果后面再赋值也是需要判断的，100条数据每次都要判断效率太慢
        String field = StudyPointsListConstants.getField(dto.getType());

        // 构建聚合条件
        Aggregation aggregation = Aggregation.newAggregation(
                // 展开数组
                unwind("list"),

                // 查询条件
                match(criteria),

                // 投影字段
                project("list.uid").and(field).as("points").andExclude("_id"),

                // 排序
                sort(Sort.Direction.DESC, "points"),

                // 跳过多少个  第一页时：(1 - 1) * 100, 就是不跳过
                skip((long) (dto.getPage() - 1) * dto.getSize()),

                // 一页有多少个
                limit(dto.getSize())
        );

        // 取得榜单数据
        List<UserPointsVO> list = mongoTemplate.aggregate(aggregation, StudyPointsList.class, UserPointsVO.class).getMappedResults();

        // 获取用户信息，构建vo
        for (UserPointsVO userPointsVO : list) {
            // 查找用户
            User user = mongoTemplate.findOne(Query.query(Criteria.where("uid").is(userPointsVO.getUid())), User.class);
            // 将用户头像，昵称，头像复制
            BeanUtils.copyProperties(user, userPointsVO);
        }
        return list;
    }

    /**
     * 查找在该知识圈下用户的积分
     *
     * @param uid
     * @param dto
     * @return
     */
    @Override
    public UserPointsVO findUserPoints(String uid, PointsListDTO dto) {
        // 校验参数
        Preconditions.checkArgument(StringUtils.isNotBlank(uid) && Objects.nonNull(dto), ResponseCodeEnum.PARAM_REQUIRE);

        // 查找到用户积分
        UserPoints userPoints = getUserPointsOne(uid, dto.getCircleId());

        //查找用户信息
        User user = mongoTemplate.findOne(Query.query(Criteria.where("uid").is(uid)), User.class);

        // 构建vo
        UserPointsVO userPointsVO = UserPointsVO.builder()
                .uid(uid)
                .points(StudyPointsListConstants.getFieldPoints(dto.getType(), userPoints))
                .build();

        // 将用户头像，昵称，头像复制
        BeanUtils.copyProperties(user, userPointsVO);
        return userPointsVO;
    }

    /**
     * 每周一凌晨4点对用户的周积分清空
     */
    @Scheduled(cron = "0 0 4 * * 1")
    public void weekPointsClear() {
        transactionTemplate.execute(status -> {
            try {
                log.info("周积分清空开始");

                mongoTemplate.upsert(new Query(), Update.update("list.$[].week_points", 0), StudyPointsList.class);

                log.info("周积分清空结束");
                return null;

            } catch (Exception e) {
                // 回滚事务
                status.setRollbackOnly();

                log.error("===> 周积分清空失败", e);

                return null;

            }
        });
    }

    /**
     * 每月一号凌晨4点对用户的月积分清空
     */
    @Scheduled(cron = "0 0 4 1 * ?")
    public void monthPointsClear() {
        transactionTemplate.execute(status -> {
            try {
                log.info("月积分清空开始");

                mongoTemplate.upsert(new Query(), Update.update("list.$[].month_points", 0), StudyPointsList.class);

                log.info("月积分清空结束");

                return null;

            } catch (Exception e) {
                // 回滚事务
                status.setRollbackOnly();

                log.error("===> 月积分清空失败", e);

                return null;

            }
        });
    }
}
